Micron Document
πŸŽ–οΈGitΠ―Ρ€Π°πŸŽ–οΈ

Commit 3d51a48da2e78c440d06ad48eb3e9a07acd00e2b


Parents : 6f5fa49
Author : James Rich <2199651+jamesarich@users.noreply.github.com>
Signature : Signature validation error
Date : 2026-04-10T11:02:55-05:00
Committer : GitHub <noreply@github.com>
Date : 2026-04-10T16:02:55Z

feat(messaging): add IME Send action to message input (#5047)

Changes
Diff

diff --git a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/Message.kt b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/Message.kt
index d598f056bc..8d9236a8ad 100644
--- a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/Message.kt
+++ b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/Message.kt
@@ -54,6 +54,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewLightDark
@@ -460,7 +461,9 @@ private fun MessageInput(
shape = RoundedCornerShape(ROUNDED_CORNER_PERCENT.toFloat()),
isError = isOverLimit,
placeholder = { Text(stringResource(Res.string.type_a_message)) },
- keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences),
+ keyboardOptions =
+ KeyboardOptions(capitalization = KeyboardCapitalization.Sentences, imeAction = ImeAction.Send),
+ onKeyboardAction = { if (canSend) onSendMessage() },
supportingText = {
if (isEnabled) { // Only show supporting text if input is enabled
Text(

diff --git a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/component/MessageScreenComponents.kt b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/component/MessageScreenComponents.kt
index dc502ef4f5..6416337dff 100644
--- a/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/component/MessageScreenComponents.kt
+++ b/feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/component/MessageScreenComponents.kt
@@ -68,7 +68,6 @@ import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.alert_bell_text
import org.meshtastic.core.resources.cancel_reply
import org.meshtastic.core.resources.clear_selection
-import org.meshtastic.core.resources.conversations
import org.meshtastic.core.resources.copy
import org.meshtastic.core.resources.delete
import org.meshtastic.core.resources.delete_messages
@@ -77,7 +76,6 @@ import org.meshtastic.core.resources.filter_disable_for_contact
import org.meshtastic.core.resources.filter_enable_for_contact
import org.meshtastic.core.resources.filter_hide_count
import org.meshtastic.core.resources.filter_show_count
-import org.meshtastic.core.resources.message_input_label
import org.meshtastic.core.resources.navigate_back
import org.meshtastic.core.resources.new_messages_below
import org.meshtastic.core.resources.overflow_menu
@@ -88,10 +86,7 @@ import org.meshtastic.core.resources.reply
import org.meshtastic.core.resources.replying_to
import org.meshtastic.core.resources.scroll_to_bottom
import org.meshtastic.core.resources.select_all
-import org.meshtastic.core.resources.send
-import org.meshtastic.core.resources.type_a_message
import org.meshtastic.core.resources.unknown
-import org.meshtastic.core.ui.component.EmptyDetailPlaceholder
import org.meshtastic.core.ui.component.MeshtasticTextDialog
import org.meshtastic.core.ui.component.NodeKeyStatusIcon
import org.meshtastic.core.ui.component.SecurityIcon
@@ -99,7 +94,6 @@ import org.meshtastic.core.ui.icon.ArrowBack
import org.meshtastic.core.ui.icon.ArrowDownward
import org.meshtastic.core.ui.icon.ChatBubbleOutline
import org.meshtastic.core.ui.icon.Close
-import org.meshtastic.core.ui.icon.Conversations
import org.meshtastic.core.ui.icon.Copy
import org.meshtastic.core.ui.icon.Delete
import org.meshtastic.core.ui.icon.FilterList
@@ -109,7 +103,6 @@ import org.meshtastic.core.ui.icon.More
import org.meshtastic.core.ui.icon.Muted
import org.meshtastic.core.ui.icon.Reply
import org.meshtastic.core.ui.icon.SelectAll
-import org.meshtastic.core.ui.icon.Send
import org.meshtastic.core.ui.icon.Unmuted
import org.meshtastic.core.ui.icon.Visibility
import org.meshtastic.core.ui.icon.VisibilityOff
@@ -600,99 +593,8 @@ fun MessageStatusDialog(
// endregion
-// region ── EmptyConversationsPlaceholder ──
-
-@Composable
-fun EmptyConversationsPlaceholder(modifier: Modifier = Modifier) {
- EmptyDetailPlaceholder(
- icon = MeshtasticIcons.Conversations,
- title = stringResource(Res.string.conversations),
- modifier = modifier,
- )
-}
-
-// endregion
-
-// region ── MessageInput ──
-
-/**
- * Shared message input field with send button, byte counter, and homoglyph encoding support.
- *
- * @param messageText The current message text.
- * @param onMessageChange Callback when the text changes.
- * @param onSendMessage Callback when the send button is pressed.
- * @param isEnabled Whether the input field should be enabled.
- * @param isHomoglyphEncodingEnabled Whether to optimize text using homoglyph encoding.
- * @param maxByteSize The maximum allowed size of the message in bytes.
- */
-@Composable
-fun MessageInput(
- messageText: String,
- onMessageChange: (String) -> Unit,
- onSendMessage: () -> Unit,
- isEnabled: Boolean,
- modifier: Modifier = Modifier,
- isHomoglyphEncodingEnabled: Boolean = false,
- maxByteSize: Int = MESSAGE_CHARACTER_LIMIT_BYTES,
-) {
- val currentText =
- if (isHomoglyphEncodingEnabled) {
- org.meshtastic.core.common.util.HomoglyphCharacterStringTransformer.optimizeUtf8StringWithHomoglyphs(
- messageText,
- )
- } else {
- messageText
- }
-
- val currentByteLength = remember(currentText) { currentText.encodeToByteArray().size }
-
- val isOverLimit = currentByteLength > maxByteSize
- val canSend = !isOverLimit && currentText.isNotEmpty() && isEnabled
-
- androidx.compose.material3.OutlinedTextField(
- modifier = modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp),
- value = messageText,
- onValueChange = onMessageChange,
- maxLines = MAX_INPUT_LINES,
- label = { Text(stringResource(Res.string.message_input_label)) },
- enabled = isEnabled,
- shape = RoundedCornerShape(ROUNDED_CORNER_PERCENT.toFloat()),
- isError = isOverLimit,
- placeholder = { Text(stringResource(Res.string.type_a_message)) },
- supportingText = {
- if (isEnabled) {
- Text(
- text = "$currentByteLength/$maxByteSize",
- style = MaterialTheme.typography.bodySmall,
- color =
- if (isOverLimit) {
- MaterialTheme.colorScheme.error
- } else {
- MaterialTheme.colorScheme.onSurfaceVariant
- },
- modifier = Modifier.fillMaxWidth(),
- textAlign = androidx.compose.ui.text.style.TextAlign.End,
- )
- }
- },
- trailingIcon = {
- IconButton(onClick = { if (canSend) onSendMessage() }, enabled = canSend) {
- Icon(imageVector = MeshtasticIcons.Send, contentDescription = stringResource(Res.string.send))
- }
- },
- )
-}
-
-// endregion
-
// region ── Utility Functions ──
-/** Maximum number of lines for the message input field. */
-private const val MAX_INPUT_LINES = 3
-
-/** Corner radius percentage for the message input field. */
-private const val ROUNDED_CORNER_PERCENT = 100
-
/** The maximum number of characters to display in the reply snippet. */
internal const val SNIPPET_CHARACTER_LIMIT = 50

Served by rngit 1.5.4 - Generated in 0.04s